/**************************************************************************************************
	$Header: /pub/cvsroot/mydns/src/mydns/tcp.c,v 1.26 2003/07/27 09:08:17 bboy Exp $

	Copyright (C) 2002, 2003  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "named.h"


/**************************************************************************************************
	ACCEPT_TCP_QUERY
**************************************************************************************************/
int
accept_tcp_query(int fd)
{
	struct sockaddr_in addr;
	int	rmt_fd, addrlen = sizeof(addr);
	TASK	*t;

	if ((rmt_fd = accept(fd, (struct sockaddr *)&addr, &addrlen)) < 0)
		return Warn("accept");
	fcntl(rmt_fd, F_SETFL, fcntl(rmt_fd, F_GETFL, 0) | O_NONBLOCK);

	t = task_init(NEED_READ, rmt_fd, SOCK_STREAM, &addr);
	if (enqueue(Tasks, t) < 0)
	{
		task_free(t);
		return (-1);
	}

#if DEBUG_ENABLED
	Debug("[%lu] %s: TCP connection accepted", t->internal_id, inet_ntoa(t->addr.sin_addr));
#endif

	return 0;
}
/*--- accept_tcp_query() ------------------------------------------------------------------------*/


/**************************************************************************************************
	READ_TCP_LENGTH
	The first two octets of a TCP question are the length.  Read them.
	Returns 0 on success, -1 on failure.
**************************************************************************************************/
static int
read_tcp_length(TASK *t)
{
	int	rv;
	char	len[2];

	if ((rv = recv(t->fd, len, 2, 0)) != 2)
	{
		if (rv < 0)
			return Warn("%s: recv (length)", inet_ntoa(t->addr.sin_addr));
		if (rv == 0)
			return (-1);
		return Warnx("%s: %s", inet_ntoa(t->addr.sin_addr), _("TCP message length invalid"));
	}

	if ((t->len = ((len[0] << 8) | (len[1]))) < DNS_HEADERSIZE)
		return Warnx("%s: %s (%d octet%s)", inet_ntoa(t->addr.sin_addr), _("TCP message too short"), t->len, S(t->len));
	if (t->len > DNS_MAXPACKETLEN_TCP)
		return Warnx("%s: %s (%d octet%s)", inet_ntoa(t->addr.sin_addr), _("TCP message too long"), t->len, S(t->len));

	if (!(t->query = calloc(1, t->len + 1)))
		Err("calloc");
	t->offset = 0;
	return (0);
}
/*--- read_tcp_length() -------------------------------------------------------------------------*/


/**************************************************************************************************
	READ_TCP_QUERY
	Returns 0 on success, -1 on failure.
**************************************************************************************************/
int
read_tcp_query(TASK *t)
{
	char		*src, *qdtop, *end;
	int		rv;
	char		qname[DNS_MAXNAMELEN+1];
	uint16_t	ancount, nscount, arcount;

	/* Read packet length if we haven't already */
	if (!t->len)
		return read_tcp_length(t);

	end = t->query + t->len;

	/* Read whatever data is ready */
	if ((rv = recv(t->fd, t->query + t->offset, t->len - t->offset, 0)) < 0)
		return Warn("%s: %s", inet_ntoa(t->addr.sin_addr), _("recv"));
	if (!rv)
		return (-1);	/* Client closed connection */
#if DEBUG_ENABLED
	Debug("[%lu] %s: 2+%d TCP octets in", t->internal_id, inet_ntoa(t->addr.sin_addr), rv);
#endif
	t->offset += rv;
	if (t->offset > t->len)
		return Warnx("%s: %s", inet_ntoa(t->addr.sin_addr), _("TCP message data too long"));
	if (t->offset < t->len)
		return 0;													/* Not finished reading */

	/* Reset offset for writing reply */
	t->offset = 0;

	/* Parse query header data */
	src = t->query;
	DNS_GET16(t->id, src);
	memcpy(&t->hdr, src, SIZE16); src += SIZE16;
	DNS_GET16(t->qdcount, src);
	DNS_GET16(ancount, src);
	DNS_GET16(nscount, src);
	DNS_GET16(arcount, src);

#if DEBUG_ENABLED
	Debug("%s: id=%u qr=%u opcode=%s aa=%u tc=%u rd=%u ra=%u z=%u rcode=%u", desctask(t),
			t->id, t->hdr.qr, mydns_opcode_str(t->hdr.opcode),
			t->hdr.aa, t->hdr.tc, t->hdr.rd, t->hdr.ra, t->hdr.z, t->hdr.rcode);
	Debug("%s: qd=%u an=%u ns=%u ar=%u", desctask(t),
			t->qdcount, ancount, nscount, arcount);
#endif

	/* Check validity of header fields */
	if (!ntohs(t->qdcount))
		return formerr(t, DNS_R_FORMERR, ERR_NO_QUESTION);

	if (t->hdr.tc)
		return formerr(t, DNS_R_FORMERR, ERR_QUESTION_TRUNCATED);

	if (ntohs(t->hdr.opcode) != DNS_OPCODE_QUERY)
		return formerr(t, DNS_R_NOTIMP, ERR_UNSUPPORTED_OPCODE);

	/* Fill in question data */
	t->qdlen = t->len - DNS_HEADERSIZE;
	if (t->qdlen <= 0)
		return formerr(t, DNS_R_FORMERR, ERR_MALFORMED_REQUEST);

	if (!(t->qd = (char *)malloc(t->qdlen)))
		Err("malloc");
	memcpy(t->qd, src, t->qdlen);
	qdtop = src;

	/* Get QNAME, QTYPE, and QCLASS */
	if (!(src = name_unencode(t->qd, t->qdlen, src, qname, sizeof(qname))))
		return formerr(t, DNS_R_FORMERR, qname[0]);
	strncpy(t->qname, qname, sizeof(t->qname)-1);

	if (src + SIZE16 > end)
		return formerr(t, DNS_R_FORMERR, ERR_MALFORMED_REQUEST);
	DNS_GET16(t->qtype, src);

	if (src + SIZE16 > t->query + t->len)
		return formerr(t, DNS_R_FORMERR, ERR_MALFORMED_REQUEST);
	DNS_GET16(t->qclass, src);

	t->qdlen = src - qdtop;

	if (!axfr_enabled && t->qtype == DNS_T_AXFR)
		return formerr(t, DNS_R_REFUSED, ERR_AXFR_NOT_ENABLED);

	if (!tcp_enabled && t->qtype != DNS_T_AXFR)
		return formerr(t, DNS_R_REFUSED, ERR_TCP_NOT_ENABLED);

	/* Check class (only IN or ANY are allowed) */
	if ((t->qclass != DNS_C_IN) && (t->qclass != DNS_C_ANY))
	{
		dnserror(t, DNS_R_NOTIMP, ERR_NO_CLASS);
		build_reply(t, 1);
		return 0;
	}

	/* If this is AXFR, fork to handle it so that other requests don't block */
	if (t->qtype == DNS_T_AXFR)
	{
		int pfd[2];													/* Parent/child pipe descriptors */
		pid_t pid, parent;

		if (pipe(pfd))
			Err("pipe");
		parent = getpid();
		if ((pid = fork()) < 0)
			return Warn("%s: fork", inet_ntoa(t->addr.sin_addr));

		if (!pid)	/* Child: Let parent know I have started */
		{
			close(pfd[0]);
			if (write(pfd[1], "OK", 2) != 2)
				Warn(_("error writing startup notification"));
			close(pfd[1]);
			axfr(t);
		}
		else
		{
			char buf[5] = "\0\0\0\0\0";

			close(pfd[1]);
			if (read(pfd[0], &buf, 4) != 2)
				Warn(_("error reading startup notification"));
			close(pfd[0]);
#if DEBUG_ENABLED
			Debug("AXFR: process started on pid %d for TCP fd %d", pid, t->fd);
#endif
		}
		return (0);
	}

	t->status = NEED_ANSWER;

	return 0;
}
/*--- read_tcp_query() --------------------------------------------------------------------------*/


/**************************************************************************************************
	WRITE_TCP_LENGTH
	Writes the length octets for TCP reply.  Returns 0 on success, -1 on failure.
**************************************************************************************************/
static int
write_tcp_length(TASK *t)
{
	char	len[2], *l;
	int	rv;

	l = len;
	DNS_PUT16(l, t->replylen);

	if ((rv = write(t->fd, len + t->offset, SIZE16 - t->offset)) < 0)
	{
		if (errno == EINTR)
			return 0;
		return Warn("%s: write (length)", inet_ntoa(t->addr.sin_addr));
	}
	if (!rv)
		return (-1);		/* Client closed connection */
	t->offset += rv;
	if (t->offset >= SIZE16)
	{
		t->len_written = 1;
		t->offset = 0;
	}
	return 0;
}
/*--- write_tcp_length() ------------------------------------------------------------------------*/


/**************************************************************************************************
	WRITE_TCP_REPLY
**************************************************************************************************/
int
write_tcp_reply(TASK *t)
{
	int rv;

	if (!t->len_written)
		return write_tcp_length(t);

	if ((rv = write(t->fd, t->reply + t->offset, t->replylen - t->offset)) < 0)
	{
		if (errno == EINTR)
			return 0;
		return Warn("%s: write", inet_ntoa(t->addr.sin_addr));
	}
	if (!rv)
		return (-1);	/* Client closed connection */
	t->offset += rv;
	if (t->offset >= t->replylen)
	{
		int rmt_fd = t->fd;
		struct sockaddr_in addr;

		/* Task complete; dequeue */
		/* For BIND8 AXFR, we leave the connection open until the remote host closes it - reset
			the connection here and get ready to answer another */
#if DEBUG_ENABLED
		Debug("%s: reply 2+%u TCP octets", desctask(t), t->replylen);
#endif
		memcpy(&addr, &t->addr, sizeof(struct sockaddr_in));

		dequeue(Tasks, t);
		if (enqueue(Tasks, task_init(NEED_READ, rmt_fd, SOCK_STREAM, &addr)) < 0)
			return (-1);
	}
	return (0);
}
/*--- write_tcp_reply() -------------------------------------------------------------------------*/

/* vi:set ts=3: */
/* NEED_PO */
